import threading
import time
import zmq
from zhelpers import zpipe


# 请求超时时间
GLOBAL_TIMEOUT = 3000
# 心跳间隔
PING_INTERVAL = 2000
# 判定服务死亡的时间
SERVER_TTL = 6000


class FreelanceClient(object):
    # 上下文
    ctx = None
    # 用于和主线程通信的套接字
    pipe = None
    agent = None

    def __init__(self):
        self.ctx = zmq.Context()
        self.pipe, peer = zpipe(self.ctx)
        self.agent = threading.Thread(target=flciapi_agent, args=(self.ctx, peer))
        self.agent.daemon = True
        self.agent.start()

    # 连接至新服务器端点
    # 消息内容：[CONNECT][endpoint]
    def connect(self, endpoint):
        print("pipe send CONNECT")
        self.pipe.send_multipart([b"CONNECT", endpoint.encode('utf-8')])
        time.sleep(0.1)

    # 发送并销毁请求，接收应答
    def request(self, msg):
        msg = [s.encode('utf-8') for s in msg]
        request = [b"REQUEST"] + msg
        print("pipe send REQUEST", request)
        self.pipe.send_multipart(request)
        reply = self.pipe.recv_multipart()
        print("pipe recv REQUEST", reply)
        status = reply.pop(0)
        if status != b"FAILED":
            return reply


# 单个服务端信息
class FreelanceServer(object):
    # 服务端端点/套接字标识
    endpoint = None
    # 是否在线
    alive = True
    # 下一次心跳时间
    ping_at = 0
    # 过期时间
    expires = 0

    def __init__(self, endpoint):
        self.endpoint = endpoint
        self.alive = True
        self.ping_at = time.time() + 1e-3*PING_INTERVAL
        self.expires = time.time() + 1e-3*SERVER_TTL

    def ping(self, socket):
        if time.time() > self.ping_at:
            msg = [self.endpoint, b"PING"]
            print("router send: ", msg)
            socket.send_multipart(msg)
            self.ping_at = time.time() + 1e-3*PING_INTERVAL

    def tickless(self, tickless):
        if tickless > self.ping_at:
            tickless = self.ping_at
        return tickless


# 后台处理程序信息
class FreelanceAgent(object):
    # 上下文
    ctx = None
    # 用于应用程序通信的套接字
    pipe = None
    # 用于服务端通信的套接字
    router = None
    # 已连接的服务端
    servers = None
    # 在线的服务端
    actives = None
    # 请求编号
    sequence = 0
    # 当前请求
    request = None
    # 当前应答
    reply = None
    # 请求过期时间
    expires = 0

    def __init__(self, ctx, pipe):
        self.ctx = ctx
        self.pipe = pipe
        self.router = ctx.socket(zmq.ROUTER)
        self.servers = {}
        self.actives = []

    def control_message(self):
        msg = self.pipe.recv_multipart()
        print("pipe recv: ", msg)
        command = msg.pop(0)

        if command == b"CONNECT":
            endpoint = msg.pop(0)
            print("I: connecting to %s..." % endpoint)
            self.router.connect(endpoint)
            server = FreelanceServer(endpoint)
            self.servers[endpoint] = server
            self.actives.append(server)
            server.ping_at = time.time() + 1e-3*PING_INTERVAL
            server.expires = time.time() + 1e-3*SERVER_TTL
        elif command == b"REQUEST":
            assert not self.request
            self.request = [str(self.sequence).encode('utf-8'), b''] + msg
            self.expires = time.time() + 1e-3*GLOBAL_TIMEOUT

    def router_message(self):
        reply = self.router.recv_multipart()
        print("router recv: ", reply)
        endpoint = reply.pop(0)
        server = self.servers[endpoint]
        if not server.alive:
            self.actives.append(server)
            server.alive = 1

        server.ping_at = time.time() + 1e-3*PING_INTERVAL
        server.expires = time.time() + 1e-3*SERVER_TTL

        sequence = reply.pop(0)
        if sequence == b"PONG":
            pass
        elif int(sequence) == self.sequence:
            self.sequence += 1
            reply = [b"OK"] + reply
            self.pipe.send_multipart(reply)
            print("pipe send: ", reply)
            self.request = None


# 异步的后台代理会维护一个服务端池，处理请求和应答
def flciapi_agent(ctx, pipe):
    agent = FreelanceAgent(ctx, pipe)
    poller = zmq.Poller()
    poller.register(agent.pipe, zmq.POLLIN)
    poller.register(agent.router, zmq.POLLIN)

    while True:
        tickless = time.time() + 3600
        if agent.request and tickless > agent.expires:
            tickless = agent.expires
            for server in agent.servers.values():
                tickless = server.tickless(tickless)
        try:
            items = dict(poller.poll(1000 * (tickless - time.time())))
        except KeyboardInterrupt:
            break

        if agent.pipe in items:
            agent.control_message()

        if agent.router in items:
            agent.router_message()

        if agent.request:
            if time.time() >= agent.expires:
                print("pipe send FAILED")
                agent.pipe.send(b"FAILED")
                agent.request = None
            else:
                while agent.actives:
                    server = agent.actives[0]
                    if time.time() >= server.expires:
                        server.alive = 0
                        agent.actives.pop(0)
                    else:
                        request = [server.endpoint] + agent.request
                        print("router send: ", request)
                        agent.router.send_multipart(request)
                        break

        for server in agent.servers.values():
            server.ping(agent.router)
